﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.Security;
using System.Runtime.InteropServices;
using MyFitness.Model;
using MyFitness.View;

namespace MyFitness.Controller
{
    class UserController
    {
        #region Declared Variables
        private MyFitnessDatabaseEntities database;

        // Dictionary to manage sessions, Key=GUID/Session ID, Value=User E-mail (ID)
        private Dictionary<string, string> sessions;
        #endregion

        /// <summary>
        /// Default Contructor
        /// </summary>
        /// <param name="db"></param>
        public UserController(MyFitnessDatabaseEntities db)
        {
            if (db == null)
                throw new ArgumentException("Parameter was not expected to be null.  Please provide a valid database.");

            database = db;

            sessions = new Dictionary<string, string>();
        }

        /// <summary>
        /// Gets a user from a Session ID.
        /// </summary>
        /// <param name="sessionid"></param>
        /// <returns></returns>
        public User GetUserFromSession(string sessionid)
        {
            string userEmail = sessions[sessionid];
            return database.Users.Find(userEmail);
        }

        /// <summary>
        /// Gets a user email address from a Session ID.
        /// </summary>
        /// <param name="sessionid"></param>
        /// <returns></returns>
        public string GetUserEmailFromSession(string sessionid)
        {
            return sessions[sessionid];
        }

        /// <summary>
        /// Gets a String from a SecureString.  
        /// </summary>
        /// <param name="ss"></param>
        /// <returns></returns>
        private string GetStringFromSecureString(SecureString ss)
        {
            // Converts a SecureString to a PTR
            IntPtr ptr = new IntPtr();
            ptr = Marshal.SecureStringToBSTR(ss);

            // Converts a PTR to a BSTR
            string s = Marshal.PtrToStringBSTR(ptr);
            Marshal.ZeroFreeBSTR(ptr);
            return s;
        }

        /// <summary>
        /// Gets a SecureString from a String.  Used for ensuring passwords are protected in the application.
        /// </summary>
        /// <param name="s"></param>
        /// <returns>Returns a SecureString representation of a given string.</returns>
        public SecureString GetSecureStringFromString(string s)
        {
            SecureString ss = new SecureString();

            foreach (char c in s)
            {
                ss.AppendChar(c);
            }

            return ss;
        }

        /// <summary>
        /// Generates an MD5 Hash of a string from the given secure string.
        /// </summary>
        /// <param name="md5Hash"></param>
        /// <param name="input"></param>
        /// <returns>Returns the MD5 Hash of the inputted string.</returns>
        private string GetMd5Hash(string input)
        {
            MD5 md5Hash = MD5.Create();

            // Convert the input string to a byte array and compute the hash. 
            byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));

            // Create a new Stringbuilder to collect the bytes 
            // and create a string.
            StringBuilder sBuilder = new StringBuilder();

            // Loop through each byte of the hashed data  
            // and format each one as a hexadecimal string. 
            for (int i = 0; i < data.Length; i++)
            {
                sBuilder.Append(data[i].ToString("x2"));
            }

            // Return the hexadecimal string. 
            return sBuilder.ToString();
        }

        /// <summary>
        /// Creates a Session between a User and the Fitness Application.
        /// </summary>
        /// <param name="u">User Object</param>
        /// <returns>Returns the Session ID for the session created.</returns>
        private string CreateSession(string useremail)
        {
            if (useremail.Length > 0)
            {
                string sessionid = Guid.NewGuid().ToString();
                sessions.Add(sessionid, useremail);

                return sessionid;
            }
            else
                return "";
        }

        /// <summary>
        /// Default Login Routine
        /// </summary>
        /// <param name="email"></param>
        /// <param name="password"></param>
        /// <returns>Returns the user SessionID for the login</returns>
        public string Login(string username, SecureString password)
        {
            try
            {
                // Looks up the username from the database.
                User u = database.Users.Find(username);

                if (u != null)
                {
                    // Gets the string from the SecureString and then computes the MD5 hash of that string.
                    string s = GetStringFromSecureString(password);
                    string hash = GetMd5Hash(s);

                    if (u.Password == hash)
                    {
                        // Creates a Session for the user.
                        string sessionid = CreateSession(u.Email);

                        return sessionid;
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            return "";
        }

        /// <summary>
        /// Logs in a user from a User object.  Called after a new user is created.
        /// </summary>
        /// <param name="u">The user object to create the user session for.</param>
        /// <returns>Returns the user SessionID for the login.</returns>
        public string Login(User u)
        {
            try
            {
                if (u == null)
                    return "";

                // Looks up the username from the database to see if they already exist.
                User existinguser = database.Users.Find(u.Email);

                // Only creates a session if the passwords match.
                if (existinguser.Password == u.Password)
                {
                    string sessionid = CreateSession(u.Email);
                    return sessionid;
                }
                else
                    return "";
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            return "";
        }

        /// <summary>
        /// Adds a new user to the database from the User Object provided.
        /// </summary>
        /// <param name="u">The user object to add to the database.</param>
        /// <returns>Returns true if the user was successfully added, otherwise false.</returns>
        public bool AddUser(User u)
        {
            try
            {
                u.Password = GetMd5Hash(u.Password);

                // Looks up the username from the database to see if they already exist.
                User existinguser = database.Users.Find(u.Email);

                // Verifying that the user does not exist, if it does then the function returns false.
                if (existinguser != null)
                    return false;

                // Adds the user to the database.
                database.Users.Add(u);
                database.SaveChanges();
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return false;
            }
        }

        /// <summary>
        /// Updates a user in the database from the User Object provided.
        /// </summary>
        /// <param name="u">The user object to update the database with.</param>
        public void UpdateUser(User u)
        {
            database.Entry(u).State = System.Data.EntityState.Modified;
            database.SaveChanges();
        }

        /// <summary>
        /// Discards the updates for a user to the database.
        /// </summary>
        public void DiscardChanges()
        {
            database.Dispose();
            database = new MyFitnessDatabaseEntities();
        }

        #region EventHandlers
        #endregion
    }
}
